package org.jdesktop.swingx.plaf.basic;

import static java.awt.event.KeyEvent.VK_CAPS_LOCK;

import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;

import org.jdesktop.beans.AbstractBean;

/**
 * A class for determining the state of the
 * {@link java.awt.event.KeyEvent.VK_CAPS_LOCK CAPS LOCK key}. It also supports
 * notification when the locking state changes.
 * <p>
 * Although it is possible to use {@link Toolkit#getLockingKeyState(int)} to
 * determine the current state of the CAPS LOCK key, that method is not
 * guaranteed to work on all platforms. This class attempts to handle those
 * shortfalls and provide an easy mechanism for listening to state changes.
 * 
 * <pre>
 * CapsLockSupport cls = CapsLockSupport.getInstance();
 * // for get the current state of the caps lock key
 * boolean currentState = cls.isCapsLockEnabled();
 * // for listening to changes in the caps lock state
 * cls.addPropertyChangeListener(&quot;capsLockEnabled&quot;, myListener);
 * </pre>
 * 
 * There is one special case to be aware of. If {@code CapsLockSupport} is not
 * able to determine the state of the CAPS LOCK key, then
 * {@link #isInitialized()} will return {@code false} until it is able to
 * introspect a {@link KeyEvent} and determine the current locking state. If
 * {@code CapsLockSupport} must use delayed initialization, it will fire a
 * property change to notify listeners that it is now in an accurate state.
 * 
 * @author kschaefer
 */
public final class CapsLockSupport extends AbstractBean implements KeyEventDispatcher {
	private boolean useToolkit;
	private boolean capsLockeEnabled;
	private boolean updateViaKeyEvent;

	private static class SingletonHolder {
		private static final CapsLockSupport INSTANCE = new CapsLockSupport();

		static {
			KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(INSTANCE);
		}
	}

	private CapsLockSupport() {
		try {
			capsLockeEnabled = Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK);
			useToolkit = true;
			updateViaKeyEvent = false;
		} catch (UnsupportedOperationException e) {
			capsLockeEnabled = false;
			useToolkit = false;
			updateViaKeyEvent = true;
		}
	}

	/**
	 * Gets the only instance of {@code CapsLockSupport}.
	 * 
	 * @return the {@code CapsLockSupport} instance
	 */
	public static CapsLockSupport getInstance() {
		return SingletonHolder.INSTANCE;
	}

	/**
	 * Determines if {@code CapsLockSupport} is accurately synchronized with the
	 * state of the CAPS LOCK key. When not initialized,
	 * {@link #isCapsLockEnabled()} will always return {@code false} .
	 * {@code CapsLockSupport} will fail to initialize only if
	 * {@code Toolkit#getLockingKeyState(int)} throws an exception; in that
	 * case, it will initialize as soon as it receives a valid key event (that
	 * can be used to determine the current locking state).
	 * 
	 * @return {@code true} if {@code CapsLockSupport} accurately knows the
	 *         state of the CAPS LOCK key
	 */
	public boolean isInitialized() {
		return useToolkit || (useToolkit ^ updateViaKeyEvent);
	}

	/**
	 * Determines the current state of the
	 * {@link java.awt.event.KeyEvent.VK_CAPS_LOCK CAPS LOCK key}.
	 * 
	 * @return {@code true} if CAPS LOCK is enabled; {@code false} otherwise
	 */
	public boolean isCapsLockEnabled() {
		if (useToolkit) {
			try {
				return Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK);
			} catch (UnsupportedOperationException shouldNeverHappen) {
				return capsLockeEnabled;
			}
		}

		return capsLockeEnabled;
	}

	void setCapsLockEnabled(boolean capsLockEnabled) {
		boolean oldValue = this.capsLockeEnabled;
		this.capsLockeEnabled = capsLockEnabled;
		firePropertyChange("capsLockEnabled", oldValue, this.capsLockeEnabled); //$NON-NLS-1$
	}

	// updateViaKeyEvent is use to find the initial state of the CAPS LOCK key
	// when the Toolkit does
	// not support it
	/**
	 * This is an implementation detail and should not be considered public.
	 */
	@Override
	public boolean dispatchKeyEvent(KeyEvent e) {
		if (e.getID() == KeyEvent.KEY_PRESSED) {
			int keyCode = e.getKeyCode();

			if (keyCode == VK_CAPS_LOCK) {
				if (!updateViaKeyEvent) {
					if (useToolkit) {
						try {
							setCapsLockEnabled(Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK));
						} catch (UnsupportedOperationException shouldNeverHappen) {
							setCapsLockEnabled(!capsLockeEnabled);
						}
					} else {
						setCapsLockEnabled(!capsLockeEnabled);
					}
				}
			} else if (updateViaKeyEvent && Character.isLetter(keyCode)) {
				if (keyCode == e.getKeyChar()) {
					capsLockeEnabled = !e.isShiftDown();
				} else {
					capsLockeEnabled = e.isShiftDown();
				}

				updateViaKeyEvent = false;
				firePropertyChange("initialized", false, true); //$NON-NLS-1$
			}
		}

		return false;
	}
}
